Expand description

Lightweight parsing for Rust proc macros

Venial is a WIP parser for Rust proc macros.

When writing proc macros that need to parse Rust code (such as attribute and derive macros), the most common solution is to use the syn crate. Syn can parse arbitrary valid Rust code, and even Rust-based DSLs, and return versatile data structures that can inspected and mutated in powerful ways.

It’s also extremely heavy. In one analysis of lqd’s early 2022 benchmark collection, the author estimates that syn is reponsible for 8% of compile times of the benchmark, which accounts for Rust’s most popular crates. There are subtleties (eg this isn’t necessarily critical path time, but syn is often in the critical path anyway), but the overall takeaway is clear: syn is expensive.

And yet, a lot of the power of syn is often unneeded. If we look at the crates that depend on syn, we can see that the 5 most downloaded are:

  • serde_derive
  • proc-macro-hack
  • pin-project-internal
  • anyhow
  • thiserror-impl

Of these, proc-macro-hack is deprecated, and the other four only need to parse basic information on a type.

Other popular reverse-dependencies of syn (such as futures-macro, tokios-macros, async-trait, etc) do use syn’s more advanced features, but there’s still room for a lightweight parser in proc-macros.

Venial is that parser.

Example

use venial::{parse_declaration, Declaration};
use quote::quote;

let enum_type = parse_declaration(quote!(
    enum Shape {
        Square(Square),
        Circle(Circle),
        Triangle(Triangle),
    }
));

let enum_type = match enum_type {
    Ok(Declaration::Enum(enum_type)) => enum_type,
    _ => unreachable!(),
};

assert_eq!(enum_type.variants[0].0.name, "Square");
assert_eq!(enum_type.variants[1].0.name, "Circle");
assert_eq!(enum_type.variants[2].0.name, "Triangle");

Structs

An outer or inner attribute.
Constant or static declaration.
Declaration of an enum.
The individual variant of an Enum.
The value of an EnumVariant, normally for c-like enums.
Convenient error type for displaying errors in your macros to users.
Keywords giving special information on a function.
A Function parameter which refers to self in some way.
A parameter of a Function
Declaration of a function.
List of generic arguments, as in Vec<i32, Alloc>.
A parameter bound in a type’s generic list.
A parameter in a type’s generic list.
The generic parameters declared right after your type’s name.
Information about a Group. This can be used to recreate the group from its inner token sequence, or to create a new group with a modified token sequence but the original group’s span information.
Declaration of an impl block.
Generic arguments deduced from a type’s GenericParamList.
Declaration of a module.
A field of a Struct or struct-like EnumVariant.
Implements a path expression according to the Rust documentation.
A segment of a Path, e.g. Type::<i32>
Comma-separated list of items.
Declaration of a struct.
A field of a tuple Struct or tuple-like EnumVariant.
Type definition. Handles both associated types (in impl blocks) or type aliases (globally).
Type expression in a TupleField or NamedField.
Declaration of an union.
Type definition. Handles both associated types (in impl blocks) or type aliases (globally).
Value expression, e.g. as the initializer of a Constant.
Visibility marker, eg pub, pub(crate), pub(super), etc.
All the stuff that comes after the where keyword.
An item in a where clause

Enums

The value of an Attribute.
The declaration of a Rust type.
A parameter of a Function
A parameter in a type’s generic list.
The group representing an impl block.
Fields of a Struct or an EnumVariant.

Functions

Parses the token stream of a type declaration.